筆記目錄

Skip to content

如何客製化 ASP.NET Core 的 Model Validation 預設錯誤訊息

TLDR

  • ASP.NET Core 預設的 Model Validation 訊息僅提供英文,可透過建立資源檔 (.resx) 進行客製化。
  • 驗證訊息分為「ModelBinding」與「ValidationMetadata」兩部分,需分別實作。
  • 透過實作 IValidationMetadataProvider 介面,可自動替換 ValidationAttribute 的預設錯誤訊息。
  • 支援多國語系時,應將資源檔設為「沒有程式碼產生」,並透過 RequestLocalizationOptions 設定 SupportedUICultures
  • 設定語系時應區分 Culture(格式化)與 UICulture(資源載入),避免混淆。

客製化 Model Validation 預設訊息

什麼情況下會遇到這個問題:當專案需要將 ASP.NET Core 內建的驗證錯誤訊息(如 RequiredAttribute 產生的 "The field is required.")轉換為中文或其他語言,且不希望手動為每個欄位設定錯誤訊息時。

ASP.NET Core 的驗證機制分為兩類:

  • ModelBinding 驗證:與資料格式相關(如型別轉換失敗)。
  • ValidationMetadata 驗證:與資料內容規則相關(如長度限制、必填)。

建立資源檔 (.resx)

首先建立資源檔存放對應的錯誤訊息。資源檔屬性設定如下:

  • 建置動作:內嵌資源
  • 複製到輸出目錄:不要複製

將所需的錯誤訊息鍵值對(Key-Value)填入資源檔中,例如 RequiredAttribute_ValidationError 對應 "{0} 欄位為必填。"

建立客製化的 ValidationMetadataProvider

為了自動替換 ValidationAttribute 的錯誤訊息,需實作 IValidationMetadataProvider

csharp
public class LocalizationValidationMetadataProvider : IValidationMetadataProvider {
    private readonly ResourceManager resourceManager;
    private readonly Type resourceType;

    public LocalizationValidationMetadataProvider(Type type) {
        resourceType = type;
        resourceManager = new ResourceManager(type);
    }

    public void CreateValidationMetadata(ValidationMetadataProviderContext context) {
        foreach (var attribute in context.ValidationMetadata.ValidatorMetadata.OfType<ValidationAttribute>()) {
            if (attribute.ErrorMessageResourceName is null) {
                bool hasErrorMessage = attribute.ErrorMessage != null;

                if (hasErrorMessage) {
                    string? defaultErrorMessage = typeof(ValidationAttribute)
                        .GetField("_defaultErrorMessage", BindingFlags.NonPublic | BindingFlags.Instance)
                        ?.GetValue(attribute) as string;

                    hasErrorMessage = attribute.ErrorMessage != defaultErrorMessage;
                }

                if (hasErrorMessage) {
                    continue;
                }

                string? name = GetMessageName(attribute);
                if (name != null && resourceManager.GetString(name) != null) {
                    attribute.ErrorMessageResourceType = resourceType;
                    attribute.ErrorMessageResourceName = name;
                    attribute.ErrorMessage = null;
                }
            }
        }
    }

    private string? GetMessageName(ValidationAttribute attr) {
        switch (attr) {
            case CompareAttribute _:
                return "CompareAttribute_MustMatch";
            case StringLengthAttribute vAttr:
                if (vAttr.MinimumLength > 0) {
                    return "StringLengthAttribute_ValidationErrorIncludingMinimum";
                }
                return "StringLengthAttribute_ValidationError";
            case DataTypeAttribute _:
                return $"{attr.GetType().Name}_Invalid";
            case ValidationAttribute _:
                return $"{attr.GetType().Name}_ValidationError";
        }

        return null;
    }
}

註冊服務

Program.cs 中註冊上述 Provider 並設定 ModelBindingMessageProvider

csharp
builder.Services.AddRazorPages()
    .AddMvcOptions(options => {
        var provider = options.ModelBindingMessageProvider;
        provider.SetAttemptedValueIsInvalidAccessor((x, y) => string.Format(ModelBindingMessage.AttemptedValueIsInvalid, x, y));
        // 其他 ModelBinding 訊息設定...
        
        options.ModelMetadataDetailsProviders.Add(new LocalizationValidationMetadataProvider(typeof(ValidationMetadataMessage)));
    });

多國語系支援

什麼情況下會遇到這個問題:當應用程式需要根據使用者的語系設定,動態切換驗證錯誤訊息的語言時。

設定語系資源檔

建立對應語系的資源檔(如 ValidationMetadataMessage.zh-TW.resx),並將其存取修飾詞設為「沒有程式碼產生」。系統會自動根據 UICulture 讀取對應的資源檔。

設定 RequestLocalization

Program.cs 中配置 RequestLocalizationOptions

csharp
WebApplication app = builder.Build();

string[] supportedCultures = new string[] { "zh-TW", "en-US" };
RequestLocalizationOptions localizationOptions = new RequestLocalizationOptions()
    .SetDefaultCulture(supportedCultures[0])
    .AddSupportedCultures(supportedCultures)
    .AddSupportedUICultures(supportedCultures);

app.UseRequestLocalization(localizationOptions);

INFO

Culture 常見誤解

  1. DefaultRequestCulture 的運作機制:它是優先序最後的 Provider。系統會依序嘗試從 QueryString、Cookie 或 Accept-Language Header 尋找 UICulture,若找不到才會使用 DefaultRequestCulture
  2. Culture 與 UICulture 的區別
    • Culture:控制日期、數值、貨幣的格式化與排序。
    • UICulture:控制載入哪一種語言的資源檔。
    • 若透過 QueryString 設定語系,正確的參數名稱應為 ui-culture 而非 culture

異動歷程

  • 2022-10-05 初版文件建立。
  • 2024-04-04 修正 ModelBindingMessage 的訊息。